/*** Include Files ***********************************************************/
#include "timer.h"
#include "cmsis_os.h"

#include "hal_timer.h"

/*** Module Macros ***********************************************************/
#define TIMER_INTERVAL          1000

#ifndef ASSERT
#define ASSERT(...)
#endif

/*** Module Types ************************************************************/
typedef struct
{
    /* if the timer started */
    bool enable;
    /* if perodic */
    bool perodic;
    /* timer trigger count/second */
    uint32_t trigger_count;
    /* timer start count/second */
    uint32_t start_count;
    /* timer callback, strongly suggest to use non-block function */
    timerCallback callback;
} sTimerCfg_t;

/*** Module Variables ********************************************************/
/* timer config for virtual timer */
static sTimerCfg_t timerCfg[TIMER_ID_NUM];

/* timer last trigger second, to check if trigger missing */
static uint32_t uTimerLastTriggerSec = 0;

/* Timer for software timer */
static void vSoftwareTimerCallback(void const *param);
osTimerDef(SOFTWARE_TIMER, vSoftwareTimerCallback);
static osTimerId softwareTimerId = NULL;

/*** Module Functions ********************************************************/
static void vInternalTimerStart(void)
{
    /* Stop the timer before start */
    osTimerStop(softwareTimerId);
    /* Start the timer at second */
    uint32_t milliSec = GET_CURRENT_MS();
    uint32_t timerTrigger = TIMER_INTERVAL - (milliSec - (milliSec/TIMER_INTERVAL*TIMER_INTERVAL));
    osTimerStart(softwareTimerId, timerTrigger);
}

void vTimerInit(void)
{
    /* Clear the timer config */
    int id = 0;
    for (id=0; id<TIMER_ID_NUM; id++)
    {
        timerCfg[id].enable = false;
    }
    /* Create the timer is needed */
    if (softwareTimerId == NULL)
    {
        softwareTimerId = osTimerCreate(osTimer(SOFTWARE_TIMER), osTimerOnce, NULL);
    }
    ASSERT(softwareTimerId, "%s(softwareTimerId is NULL)", __func__);
    vInternalTimerStart();
}

void vTimerCreate(eTimerId_t id, bool perodic, timerCallback callback)
{
    ASSERT(id >= TIMER_ID_CHG && id < TIMER_ID_NUM, "%s(id out of range)", __func__);
    /* Set the virtual timer callback */
    timerCfg[id].callback = callback;
    /* Set perodic */
    timerCfg[id].perodic = perodic;
}

void vTimerStart(eTimerId_t id, uint32_t sec)
{
    ASSERT(id >= TIMER_ID_CHG && id < TIMER_ID_NUM, "%s(id out of range)", __func__);
    /* Start the virtual timer */
    timerCfg[id].enable = true;
    /* Set the virtual timer config */
    timerCfg[id].trigger_count = sec;
    /* Record start time */
    timerCfg[id].start_count = GET_CURRENT_MS() / TIMER_INTERVAL;
}

void vTimerStop(eTimerId_t id)
{
    ASSERT(id >= TIMER_ID_CHG && id < TIMER_ID_NUM, "%s(id out of range)", __func__);
    /* Stop the virtual timer */
    timerCfg[id].enable = false;
}

static void vSoftwareTimerCallback(void const *param)
{
    /* handle the virtual timer */
    int id = 0;
    /* get current time in ms */
    uint32_t uCurrentSec = GET_CURRENT_MS() / TIMER_INTERVAL;
    /* check if trigger missing */
    if (uTimerLastTriggerSec && (uTimerLastTriggerSec + 1 != uCurrentSec))
    {
        // If you don't want to assert, you can handle the missing trigger event too.
        // But handle missing trigger need more complex algorithm.
        ASSERT(0, "%s() timer trigger missing");
    }
    else
    {
        // record the trigger time in second.
        uTimerLastTriggerSec = uCurrentSec;
    }
    /* count the timer run count */
    uint32_t sec_count = uCurrentSec - timerCfg[id].start_count;

    for (id=0; id<TIMER_ID_NUM; id++)
    {
        /* Skip when:
         * 1. timer is disable;
         * 2. timer's callback is null
         * 3. timer's trigger is 0
         */
        if ((!timerCfg[id].enable) || (timerCfg[id].callback == NULL) || (!timerCfg[id].trigger_count))
        {
            continue;
        }
        /* handle virtual timer callback when the second count is mutiple of trigger count */
        if (sec_count % timerCfg[id].trigger_count == 0)
        {
            timerCfg[id].callback();
            /* disable timer if not perodic */
            if (!timerCfg[id].perodic)
            {
                timerCfg[id].enable = false;
            }
        }
    }
    /* Start the software timer to do next circle */
    vInternalTimerStart();
}